ifelse(!dir.exists(outdir), dir.create(outdir), FALSE)
[1] TRUE
model.cca <- readRDS("~/models/modelCCA_reference_hvg_PBMC_SCElist_20191105.RDS")
model.liger <- readRDS("~/models/modelLiger_reference_hvg_PBMC_SCElist_20191105.RDS")
model.conos <- readRDS("~/models/modelConos_reference_hvg_PBMC_SCElist_20191105.RDS")
seu.cca <- readRDS("~/models/labelTransferCCA_reference_hvg_PBMC_SCElist_20191105.RDS")
# seu.cca.union <- readRDS("~/models/labelTransferCCA_PBMC_SCElist_20191105.RDS")
seu.liger <- readRDS("~/models/labelTransferLiger_reference_hvg_PBMC_SCElist_20191105.RDS")
seu.conos <- readRDS("~/models/labelTransferConos_reference_hvg_PBMC_SCElist_20191105.RDS")
integrate_features <- model.cca$model@anchor.features
int.list <- list(CCA=seu.cca, Liger=seu.liger, Conos=seu.conos)
## Make method color palette
method.palette <- brewer_palette_4_values(names(int.list), "Set1")

Embeddings

Visualize label transfer on original ATAC data (embedded SnapATAC bins)

## Load original data
orig.ATAC <- readRDS("~/my_data/10X_data/atac_pbmc_10k_nextgem.snapATAC.RDS")
sce.list <- readRDS("~/my_data/10X_data/PBMC_SCElist_20191105.RDS")
orig.RNA <- sce.list$RNA
Loading required package: SingleCellExperiment
Loading required package: SummarizedExperiment
Loading required package: GenomicRanges
Loading required package: stats4
Loading required package: BiocGenerics
Loading required package: parallel

Attaching package: ‘BiocGenerics’

The following objects are masked from ‘package:parallel’:

    clusterApply, clusterApplyLB, clusterCall, clusterEvalQ, clusterExport, clusterMap, parApply,
    parCapply, parLapply, parLapplyLB, parRapply, parSapply, parSapplyLB

The following objects are masked from ‘package:dplyr’:

    combine, intersect, setdiff, union

The following object is masked from ‘package:Matrix’:

    which

The following objects are masked from ‘package:stats’:

    IQR, mad, sd, var, xtabs

The following objects are masked from ‘package:base’:

    anyDuplicated, append, as.data.frame, basename, cbind, colnames, dirname, do.call, duplicated,
    eval, evalq, Filter, Find, get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply, match,
    mget, order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank, rbind, Reduce, rownames,
    sapply, setdiff, sort, table, tapply, union, unique, unsplit, which, which.max, which.min

Loading required package: S4Vectors

Attaching package: ‘S4Vectors’

The following objects are masked from ‘package:dplyr’:

    first, rename

The following object is masked from ‘package:tidyr’:

    expand

The following object is masked from ‘package:Matrix’:

    expand

The following object is masked from ‘package:base’:

    expand.grid

Loading required package: IRanges

Attaching package: ‘IRanges’

The following objects are masked from ‘package:dplyr’:

    collapse, desc, slice

The following object is masked from ‘package:purrr’:

    reduce

Loading required package: GenomeInfoDb
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with 'browseVignettes()'. To cite Bioconductor,
    see 'citation("Biobase")', and for packages 'citation("pkgname")'.

Loading required package: DelayedArray
Loading required package: matrixStats

Attaching package: ‘matrixStats’

The following objects are masked from ‘package:Biobase’:

    anyMissing, rowMedians

The following object is masked from ‘package:dplyr’:

    count

Loading required package: BiocParallel

Attaching package: ‘DelayedArray’

The following objects are masked from ‘package:matrixStats’:

    colMaxs, colMins, colRanges, rowMaxs, rowMins, rowRanges

The following object is masked from ‘package:purrr’:

    simplify

The following objects are masked from ‘package:base’:

    aperm, apply, rowsum


Attaching package: ‘SummarizedExperiment’

The following object is masked from ‘package:Seurat’:

    Assays
## Make SeuratObjects
atac.seu <- snapToSeurat(
    obj=orig.ATAC, 
    eigs.dims=1:20, 
    norm=TRUE,
    scale=TRUE
    )
Epoch: checking input parameters ... 
Non-unique features (rownames) present in the input matrix, making uniqueFeature names cannot have underscores ('_'), replacing with dashes ('-')Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Centering and scaling data matrix

  |                                                                                                             
  |                                                                                                       |   0%
  |                                                                                                             
  |==                                                                                                     |   2%
  |                                                                                                             
  |====                                                                                                   |   4%
  |                                                                                                             
  |=====                                                                                                  |   5%
  |                                                                                                             
  |=======                                                                                                |   7%
  |                                                                                                             
  |=========                                                                                              |   9%
  |                                                                                                             
  |===========                                                                                            |  11%
  |                                                                                                             
  |=============                                                                                          |  12%
  |                                                                                                             
  |==============                                                                                         |  14%
  |                                                                                                             
  |================                                                                                       |  16%
  |                                                                                                             
  |==================                                                                                     |  18%
  |                                                                                                             
  |====================                                                                                   |  19%
  |                                                                                                             
  |======================                                                                                 |  21%
  |                                                                                                             
  |=======================                                                                                |  23%
  |                                                                                                             
  |=========================                                                                              |  25%
  |                                                                                                             
  |===========================                                                                            |  26%
  |                                                                                                             
  |=============================                                                                          |  28%
  |                                                                                                             
  |===============================                                                                        |  30%
  |                                                                                                             
  |=================================                                                                      |  32%
  |                                                                                                             
  |==================================                                                                     |  33%
  |                                                                                                             
  |====================================                                                                   |  35%
  |                                                                                                             
  |======================================                                                                 |  37%
  |                                                                                                             
  |========================================                                                               |  39%
  |                                                                                                             
  |==========================================                                                             |  40%
  |                                                                                                             
  |===========================================                                                            |  42%
  |                                                                                                             
  |=============================================                                                          |  44%
  |                                                                                                             
  |===============================================                                                        |  46%
  |                                                                                                             
  |=================================================                                                      |  47%
  |                                                                                                             
  |===================================================                                                    |  49%
  |                                                                                                             
  |====================================================                                                   |  51%
  |                                                                                                             
  |======================================================                                                 |  53%
  |                                                                                                             
  |========================================================                                               |  54%
  |                                                                                                             
  |==========================================================                                             |  56%
  |                                                                                                             
  |============================================================                                           |  58%
  |                                                                                                             
  |=============================================================                                          |  60%
  |                                                                                                             
  |===============================================================                                        |  61%
  |                                                                                                             
  |=================================================================                                      |  63%
  |                                                                                                             
  |===================================================================                                    |  65%
  |                                                                                                             
  |=====================================================================                                  |  67%
  |                                                                                                             
  |======================================================================                                 |  68%
  |                                                                                                             
  |========================================================================                               |  70%
  |                                                                                                             
  |==========================================================================                             |  72%
  |                                                                                                             
  |============================================================================                           |  74%
  |                                                                                                             
  |==============================================================================                         |  75%
  |                                                                                                             
  |================================================================================                       |  77%
  |                                                                                                             
  |=================================================================================                      |  79%
  |                                                                                                             
  |===================================================================================                    |  81%
  |                                                                                                             
  |=====================================================================================                  |  82%
  |                                                                                                             
  |=======================================================================================                |  84%
  |                                                                                                             
  |=========================================================================================              |  86%
  |                                                                                                             
  |==========================================================================================             |  88%
  |                                                                                                             
  |============================================================================================           |  89%
  |                                                                                                             
  |==============================================================================================         |  91%
  |                                                                                                             
  |================================================================================================       |  93%
  |                                                                                                             
  |==================================================================================================     |  95%
  |                                                                                                             
  |===================================================================================================    |  96%
  |                                                                                                             
  |=====================================================================================================  |  98%
  |                                                                                                             
  |=======================================================================================================| 100%
atac.seu <- RenameCells(atac.seu, new.names = orig.ATAC@metaData$barcode)
## Add cell type predictions
getPredictedLabels <- function(seu.int, int.name, id.col="predicted.id", score.col="score"){
  pred.df <- seu.int$ATAC@meta.data[,c(id.col, score.col), drop=F] 
  rownames(pred.df) <- str_remove(rownames(pred.df), "^ATAC_")
  colnames(pred.df) <- c(str_c("predicted.id", "_", int.name), str_c("score", "_", int.name))
  pred.df
  }
pred.cca <- getPredictedLabels(seu.cca, "CCA", score.col = "prediction.score.max")
pred.liger <- getPredictedLabels(seu.liger, "Liger")
pred.conos <- getPredictedLabels(seu.conos, "Conos")
# pred.cca.union <- getPredictedLabels(seu.cca.union, "CCA.union", score.col = "prediction.score.max")
# cbind(pred.cca, pred.cca.union)
if (all(rownames(pred.conos) == rownames(pred.cca)) & all(rownames(pred.conos) == rownames(pred.liger))) {
  atac.seu <- AddMetaData(atac.seu, metadata = cbind(pred.cca, pred.liger, pred.conos))
} else {
  stop("Non corresponding cell names")
}

plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
  If you'd like to see this geom implemented,
  Please open an issue with your example code at
  https://github.com/ropensci/plotly/issues
orig.RNA.seu <- as.Seurat(orig.RNA)
orig.RNA.seu <- FindVariableFeatures(orig.RNA.seu)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
orig.RNA.seu <- ScaleData(orig.RNA.seu)
Centering and scaling data matrix

  |                                                                                                                          
  |                                                                                                                    |   0%
  |                                                                                                                          
  |==========================================================                                                          |  50%
  |                                                                                                                          
  |====================================================================================================================| 100%
orig.RNA.seu <- RunPCA(orig.RNA.seu)
PC_ 1 
Positive:  LTB, CD3E, TRAC, CD3D, TRBC2, LCK, CD3G, IL32, ETS1, IL7R 
       TCF7, CD247, CD27, CD7, BCL11B, CD69, CD2, ISG20, SPOCK2, TRBC1 
       GZMM, ARL4C, LAT, FCMR, CCR7, RORA, SYNE2, RASGRP1, LINC00861, LEF1 
Negative:  CSTA, MNDA, SERPINA1, FGL2, FCN1, NCF2, CPVL, CD68, MPEG1, CST3 
       MS4A6A, CD14, CLEC7A, CD36, AC020656.1, SLC7A7, VCAN, CSF3R, TGFBI, CFD 
       TNFAIP2, CYBB, KLF4, SPI1, GRN, S100A12, TNFSF13B, KCTD12, CFP, TIMP2 
PC_ 2 
Positive:  ANXA1, IL32, CD3E, CD247, S100A4, LCK, TMSB4X, GZMM, CD7, CD3D 
       S100A10, CD3G, ITGB2, TRBC1, TRAC, CD2, IL7R, LAT, KLRB1, RORA 
       GZMA, ZAP70, CTSW, GAPDH, NEAT1, BCL11B, CST7, S100A6, PRF1, SAMD3 
Negative:  IGHM, BANK1, CD79A, SPIB, FAM129C, MS4A1, CD22, IGHD, TSPAN13, LINC00926 
       BCL11A, HLA-DQA1, VPREB3, TNFRSF13C, TCL1A, BLNK, BLK, RALGPS2, COBLL1, JCHAIN 
       MZB1, HLA-DQB1, FCRL5, FCRLA, HLA-DQA2, CD79B, AFF3, FCRL1, TCF4, FCRL2 
PC_ 3 
Positive:  LTB, CCR7, IL7R, FOSB, MAL, TRABD2A, LEF1, CD3G, TCF7, CD3D 
       CD27, VIM, SLC2A3, COTL1, TRAC, BIRC3, CAMK4, TSHZ2, PASK, NOSIP 
       ZFP36L2, FHIT, FOS, NCF1, INPP4B, MYC, MS4A1, NELL2, TNFRSF13C, ADTRP 
Negative:  GZMB, CLIC3, KLRF1, KLRD1, NKG7, PRF1, GNLY, SPON2, CST7, FGFBP2 
       TRDC, GZMA, ADGRG1, CCL4, C12orf75, GZMH, MATK, TBX21, PTGDR, CCL5 
       S1PR5, HOPX, CD160, AKR1C3, PTCRA, FCGR3A, TTC38, RHOC, MYOM2, SLAMF7 
PC_ 4 
Positive:  GZMB, KLRD1, CYBA, KLRF1, PRF1, CLIC3, NKG7, FGFBP2, SPON2, GNLY 
       CST7, MT-CO1, GZMA, CCL4, ADGRG1, TRDC, HOPX, MATK, GZMH, TBX21 
       FCGR3A, S1PR5, CD160, MYOM2, AKR1C3, TTC38, IL2RB, XCL2, APOBEC3G, SH2D1B 
Negative:  TUBB1, GP9, CMTM5, CLDN5, TREML1, TMEM40, CTTN, CAVIN2, CLEC1B, ITGA2B 
       PF4, GNG11, C2orf88, ACRBP, AC147651.1, HIST1H2BJ, CLU, ARHGAP6, SH3BGRL2, GMPR 
       AP001189.1, PDZK1IP1, GNAZ, AC090409.1, SMOX, SPARC, MPIG6B, ESAM, SCN1B, MYL9 
PC_ 5 
Positive:  SCT, CLEC4C, LILRA4, LRRC26, TNFRSF21, SCAMP5, PHEX, KCNK17, PACSIN1, SCN9A 
       SMPD3, DNASE1L3, CYP46A1, RHEX, TPM2, MAP1A, SERPINF1, SHD, LAMP5, IL3RA 
       LINC00996, PPM1J, ASIP, AL096865.1, AC097375.1, SMIM5, CIB2, GAS6, ZFAT, CUX2 
Negative:  MS4A1, LINC00926, CD22, CD79A, BANK1, PDLIM1, CD79B, IGHD, VPREB3, TNFRSF13C 
       KLRF1, FGFBP2, KLRD1, FCRL1, RALGPS2, ADGRG1, FCER2, GNLY, FCRL2, TRDC 
       CCL4, HOPX, PAX5, CD72, FCRL5, PRF1, CST7, ADAM28, PTGDR, SWAP70 
orig.RNA.seu <- RunUMAP(orig.RNA.seu, dims=1:30)
15:52:11 UMAP embedding parameters a = 0.9922 b = 1.112
15:52:12 Read 5607 rows and found 30 numeric columns
15:52:12 Using Annoy for neighbor search, n_neighbors = 30
15:52:12 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:52:13 Writing NN index file to temp file /tmp/RtmpXEhLFb/file9c191c14dd
15:52:13 Searching Annoy index using 1 thread, search_k = 3000
15:52:14 Annoy recall = 100%
15:52:16 Commencing smooth kNN distance calibration using 1 thread
15:52:18 Initializing from normalized Laplacian + noise
15:52:19 Commencing optimization for 500 epochs, with 240156 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:52:33 Optimization finished
DimPlot(orig.RNA.seu, group.by="annotation", label = TRUE)

Prediction score

Quantifies the uncertainty of the prediction. Calculated differently for every method, but used to define which cells are “unassigned”.

ggarrange(predict_score_hist, predict_score_cumedist, common.legend = TRUE, widths = c(0.8, 1.2),
          labels=c("A", "B")) +
  ggsave(paste0(outdir, "prediction_score_distribution.pdf"), height = 6, width = 10)
Removed 180 rows containing non-finite values (stat_ecdf).

Cell type composition

Compare cell type fractions (w uncertainty)

pred.labels.df %>%
  group_by(method) %>%
  drop_na() %>%
  mutate(tot.cells=n()) %>%
  ungroup() %>%
  group_by(method, predicted.id) %>%
  summarise(tot.label = n(), tot.cells = max(tot.cells), mean.score=mean(score)) %>%
  mutate(frac.label=tot.label/tot.cells) %>%
  # bind_rows(orig.frac.df) %>%
  ggplot(aes(method, predicted.id)) +
  geom_point(aes(color=mean.score, size=frac.label)) +
  scale_color_continuous() +
  scale_color_gradient(low ="grey", high="blue") +
  theme_classic(base_size = 16)
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

Does the uncertainty depend on the size of the cluster?

original size?

pred.labels.df %>%
  group_by(method) %>%
  drop_na() %>%
  mutate(tot.cells=n()) %>%
  ungroup() %>%
  group_by(method, predicted.id) %>%
  summarise(tot.label = n(), tot.cells = max(tot.cells), mean.score=median(score), sd.score=mad(score)) %>%
  mutate(frac.label=tot.label/tot.cells) %>%
  left_join(select(orig.frac.df, predicted.id, frac.label), by="predicted.id", suffix=c(".int",".original")) %>%
  # bind_rows(orig.frac.df) %>%
  ggplot(aes(frac.label.original, mean.score, color=method)) +
  geom_point(size=2) +
  geom_errorbar(aes(ymin=mean.score-sd.score, ymax=mean.score+sd.score), alpha=0.6) +
  scale_color_brewer(palette="Set1") +
  facet_grid(.~method) +
  theme_bw(base_size = 16)
Column `predicted.id` joining character vector and factor, coercing into character vector

Scoring per cell type

dodge <- position_dodge(width=0.9)
pred.labels.df %>%
  group_by(method, predicted.id) %>%
  summarise(score.mean=mean(score, na.rm=F), score.cv = sd(score)/mean(score), n=n()) %>%
  # ggplot(aes(predicted.id, score.mean, fill=method)) +
  # geom_col( position = dodge) +
  ggplot(aes(predicted.id, method)) +
  geom_point(aes(size=score.mean, color=score.mean)) +
  scale_color_gradient(low="white", high = "blue") +
  # geom_errorbar(aes(ymin=score.mean-score.cv, ymax=score.mean+score.cv), position=dodge, width=0.25)  +
  coord_flip() +
  theme_bw(base_size = 16)

pred.labels.df %>%
  ggplot(aes(predicted.id, score, color=method)) +
  geom_boxplot() +
  coord_flip() +
  scale_color_brewer(palette="Set1")

Agreement with unsupervised clustering of ATAC data

Calculate which fractions of NNs in bin based graph of ATAC cells have the same annotation

full_join(pred.labels.df, knn_score_df) %>%
  ggplot(aes(KNN_score, color=method)) +
  stat_ecdf() +
  facet_wrap("predicted.id") +
  scale_color_brewer(palette = "Set1") +
  coord_fixed()
Joining, by = c("cell", "method")

map(cell.types, ~ ggarrange(plot_KNNecdf(.x), UMAPs_cluster(.x), nrow = 2, heights = c(1,0.8)))
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

[[8]]

[[9]]

[[10]]

[[11]]

[[12]]

[[13]]

Which cells are inconsistently aligned?

Closer look at Liger

Compare feature selection strategy

ggarrange(predict_score_hist, predict_score_cumedist, common.legend = TRUE, widths = c(0.8, 1.2),
          labels=c("A", "B")) 
Removed 251 rows containing non-finite values (stat_bin).Removed 251 rows containing non-finite values (stat_bin).Removed 411 rows containing non-finite values (stat_ecdf).

Thoughts

  • Conos scores a lot of cells with high confidence, but fails to assign cells to difficult clusters
  • CCA resembles the composition of the RNA data better, but curious that the other methods identify way more
LS0tCnRpdGxlOiAiTGFiZWwgdHJhbnNmZXIgRURBIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KFNuYXBBVEFDKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEZXNjVG9vbHMpICAjIDQgQVVDIGZ1bmN0aW9uCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShnZ2FsbHV2aWFsKSAgIyA0IHJpdmVyIHBsb3QKbGlicmFyeShnZ3B1YnIpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL3V0aWxzLlIiKQoKZ2dfY29sb3JfaHVlIDwtIGZ1bmN0aW9uKG4pIHsKICBodWVzID0gc2VxKDE1LCAzNzUsIGxlbmd0aCA9IG4gKyAxKQogIGhjbChoID0gaHVlcywgbCA9IDY1LCBjID0gMTAwKVsxOm5dCn0KCiMjIE1ha2Ugb3V0cHV0IGRpcmVjdG9yeQpvdXRkaXIgPC0gIn4vbXVsdGlPbWljX2JlbmNobWFyay9vdXRwdXQvMjAxOTExMDZfbGFiZWxUcmFuc2ZlckVEQV9QQk1DLyIKaWZlbHNlKCFkaXIuZXhpc3RzKG91dGRpciksIGRpci5jcmVhdGUob3V0ZGlyKSwgRkFMU0UpCgpgYGAKCgpgYGB7cn0KbW9kZWwuY2NhIDwtIHJlYWRSRFMoIn4vbW9kZWxzL21vZGVsQ0NBX3JlZmVyZW5jZV9odmdfUEJNQ19TQ0VsaXN0XzIwMTkxMTA1LlJEUyIpCm1vZGVsLmxpZ2VyIDwtIHJlYWRSRFMoIn4vbW9kZWxzL21vZGVsTGlnZXJfcmVmZXJlbmNlX2h2Z19QQk1DX1NDRWxpc3RfMjAxOTExMDUuUkRTIikKbW9kZWwuY29ub3MgPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxDb25vc19yZWZlcmVuY2VfaHZnX1BCTUNfU0NFbGlzdF8yMDE5MTEwNS5SRFMiKQoKc2V1LmNjYSA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyQ0NBX3JlZmVyZW5jZV9odmdfUEJNQ19TQ0VsaXN0XzIwMTkxMTA1LlJEUyIpCnNldS5saWdlciA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyTGlnZXJfcmVmZXJlbmNlX2h2Z19QQk1DX1NDRWxpc3RfMjAxOTExMDUuUkRTIikKc2V1LmNvbm9zIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJDb25vc19yZWZlcmVuY2VfaHZnX1BCTUNfU0NFbGlzdF8yMDE5MTEwNS5SRFMiKQoKaW50ZWdyYXRlX2ZlYXR1cmVzIDwtIG1vZGVsLmNjYSRtb2RlbEBhbmNob3IuZmVhdHVyZXMKCmludC5saXN0IDwtIGxpc3QoQ0NBPXNldS5jY2EsIExpZ2VyPXNldS5saWdlciwgQ29ub3M9c2V1LmNvbm9zKQoKIyMgTWFrZSBtZXRob2QgY29sb3IgcGFsZXR0ZQptZXRob2QucGFsZXR0ZSA8LSBicmV3ZXJfcGFsZXR0ZV80X3ZhbHVlcyhuYW1lcyhpbnQubGlzdCksICJTZXQxIikKCmBgYAoKIyMjIEVtYmVkZGluZ3MKVmlzdWFsaXplIGxhYmVsIHRyYW5zZmVyIG9uIG9yaWdpbmFsIEFUQUMgZGF0YSAoZW1iZWRkZWQgU25hcEFUQUMgYmlucykKYGBge3J9CiMjIExvYWQgb3JpZ2luYWwgZGF0YQpvcmlnLkFUQUMgPC0gcmVhZFJEUygifi9teV9kYXRhLzEwWF9kYXRhL2F0YWNfcGJtY18xMGtfbmV4dGdlbS5zbmFwQVRBQy5SRFMiKQpzY2UubGlzdCA8LSByZWFkUkRTKCJ+L215X2RhdGEvMTBYX2RhdGEvUEJNQ19TQ0VsaXN0XzIwMTkxMTA1LlJEUyIpCm9yaWcuUk5BIDwtIHNjZS5saXN0JFJOQQoKIyMgTWFrZSBTZXVyYXRPYmplY3RzCmF0YWMuc2V1IDwtIHNuYXBUb1NldXJhdCgKICAgIG9iaj1vcmlnLkFUQUMsIAogICAgZWlncy5kaW1zPTE6MjAsIAogICAgbm9ybT1UUlVFLAogICAgc2NhbGU9VFJVRQogICAgKQphdGFjLnNldSA8LSBSZW5hbWVDZWxscyhhdGFjLnNldSwgbmV3Lm5hbWVzID0gb3JpZy5BVEFDQG1ldGFEYXRhJGJhcmNvZGUpCgojIyBBZGQgY2VsbCB0eXBlIHByZWRpY3Rpb25zCmdldFByZWRpY3RlZExhYmVscyA8LSBmdW5jdGlvbihzZXUuaW50LCBpbnQubmFtZSwgaWQuY29sPSJwcmVkaWN0ZWQuaWQiLCBzY29yZS5jb2w9InNjb3JlIil7CiAgcHJlZC5kZiA8LSBzZXUuaW50JEFUQUNAbWV0YS5kYXRhWyxjKGlkLmNvbCwgc2NvcmUuY29sKSwgZHJvcD1GXSAKICByb3duYW1lcyhwcmVkLmRmKSA8LSBzdHJfcmVtb3ZlKHJvd25hbWVzKHByZWQuZGYpLCAiXkFUQUNfIikKICBjb2xuYW1lcyhwcmVkLmRmKSA8LSBjKHN0cl9jKCJwcmVkaWN0ZWQuaWQiLCAiXyIsIGludC5uYW1lKSwgc3RyX2MoInNjb3JlIiwgIl8iLCBpbnQubmFtZSkpCiAgcHJlZC5kZgogIH0KCnByZWQuY2NhIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY2NhLCAiQ0NBIiwgc2NvcmUuY29sID0gInByZWRpY3Rpb24uc2NvcmUubWF4IikKcHJlZC5saWdlciA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmxpZ2VyLCAiTGlnZXIiKQpwcmVkLmNvbm9zIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY29ub3MsICJDb25vcyIpCgojIHByZWQuY2NhLnVuaW9uIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY2NhLnVuaW9uLCAiQ0NBLnVuaW9uIiwgc2NvcmUuY29sID0gInByZWRpY3Rpb24uc2NvcmUubWF4IikKIyBjYmluZChwcmVkLmNjYSwgcHJlZC5jY2EudW5pb24pCgoKaWYgKGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmNjYSkpICYgYWxsKHJvd25hbWVzKHByZWQuY29ub3MpID09IHJvd25hbWVzKHByZWQubGlnZXIpKSkgewogIGF0YWMuc2V1IDwtIEFkZE1ldGFEYXRhKGF0YWMuc2V1LCBtZXRhZGF0YSA9IGNiaW5kKHByZWQuY2NhLCBwcmVkLmxpZ2VyLCBwcmVkLmNvbm9zKSkKfSBlbHNlIHsKICBzdG9wKCJOb24gY29ycmVzcG9uZGluZyBjZWxsIG5hbWVzIikKfQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xOH0KIyMgbWFrZSBjZWxsIHR5cGUgcGFsZXR0ZQpjZWxsLnR5cGVzIDwtIGxldmVscyhzZXUuY2NhJFJOQSRhbm5vdGF0aW9uKQpjZWxsLnR5cGUucGFsIDwtIHNldE5hbWVzKGdnX2NvbG9yX2h1ZShsZW5ndGgoY2VsbC50eXBlcykpLCBjZWxsLnR5cGVzKQoKYXRhYy5zZXUgPC0gUnVuVU1BUChhdGFjLnNldSwgcmVkdWN0aW9uID0gIlNuYXBBVEFDIiwgcmVkdWN0aW9uLm5hbWUgPSAidW1hcC5zbmFwIiwgZGltcz0xOjIwKQoKZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcGxvdGxpc3QgPSBsaXN0KAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiICAsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfTGlnZXIiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiTGlnZXIiKSwKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ29ub3MiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQogICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xCikKYGBgCgpgYGB7cn0KcGwgPC0gICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfTGlnZXIiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQpwbG90bHk6OmdncGxvdGx5KHBsKQpgYGAKCgpgYGB7cn0Kb3JpZy5STkEuc2V1IDwtIGFzLlNldXJhdChvcmlnLlJOQSkKb3JpZy5STkEuc2V1IDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKG9yaWcuUk5BLnNldSkKb3JpZy5STkEuc2V1IDwtIFNjYWxlRGF0YShvcmlnLlJOQS5zZXUpCm9yaWcuUk5BLnNldSA8LSBSdW5QQ0Eob3JpZy5STkEuc2V1KQpvcmlnLlJOQS5zZXUgPC0gUnVuVU1BUChvcmlnLlJOQS5zZXUsIGRpbXM9MTozMCkKCkRpbVBsb3Qob3JpZy5STkEuc2V1LCBncm91cC5ieT0iYW5ub3RhdGlvbiIsIGxhYmVsID0gVFJVRSkKYGBgCgojIyBQcmVkaWN0aW9uIHNjb3JlClF1YW50aWZpZXMgdGhlIHVuY2VydGFpbnR5IG9mIHRoZSBwcmVkaWN0aW9uLiBDYWxjdWxhdGVkIGRpZmZlcmVudGx5IGZvciBldmVyeSBtZXRob2QsIGJ1dCB1c2VkIHRvIGRlZmluZSB3aGljaCBjZWxscyBhcmUgInVuYXNzaWduZWQiLgoKCmBgYHtyfQpvcmlnLmNvbXBvc2l0aW9uIDwtIG9yaWcuUk5BJGFubm90YXRpb24Kb3JpZy5mcmFjIDwtIHRhYmxlKG9yaWcuY29tcG9zaXRpb24pL2xlbmd0aChvcmlnLmNvbXBvc2l0aW9uKQoKb3JpZy5mcmFjLmRmIDwtIGRhdGEuZnJhbWUob3JpZy5mcmFjKSAlPiUKICBkcGx5cjo6cmVuYW1lKHByZWRpY3RlZC5pZD1vcmlnLmNvbXBvc2l0aW9uLCBmcmFjLmxhYmVsPUZyZXEpICU+JQogIG11dGF0ZShtZXRob2Q9Im9yaWdpbmFsLlJOQSIpCgpzY29yZV9jb2xzIDwtIHN0cl9zdWJzZXQoY29sbmFtZXMoYXRhYy5zZXVAbWV0YS5kYXRhKSwgJ3Njb3JlXycpCmxhYmVsX2NvbHMgPC0gc3RyX3N1YnNldChjb2xuYW1lcyhhdGFjLnNldUBtZXRhLmRhdGEpLCAncHJlZGljdGVkLmlkXycpCgpwcmVkLmxhYmVscy5kZiA8LSBpbWFwKGxpc3QoQ0NBPXByZWQuY2NhLCBMaWdlcj1wcmVkLmxpZ2VyLCBDb25vcz1wcmVkLmNvbm9zKSwgfiAKICAgICAgcm93bmFtZXNfdG9fY29sdW1uKC54LCAiY2VsbCIpICU+JQogICAgICByZW5hbWVfYWxsKGZ1bnMoc3RyX3JlbW92ZSguLCBzdHJfYygiXyIsLnkpKSkpICU+JQogICAgICBtdXRhdGUobWV0aG9kPS55KQogICAgKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgbXV0YXRlKHNjb3JlPWlmZWxzZShpcy5uYShzY29yZSksIDAsIHNjb3JlKSkKCnByZWRpY3Rfc2NvcmVfaGlzdCA8LSAKICBwcmVkLmxhYmVscy5kZiAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBmaWxsPW1ldGhvZCkpICsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjgsIGJpbnM9NDApICsKICBmYWNldF9ncmlkKG1ldGhvZCB+LikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgeGxhYigiTGFiZWwgcHJlZGljdGlvbiBzY29yZSIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKY3V0b2ZmcyA8LSBzZXEoMCwxLDAuMDUpCnByZWRpY3Rfc2NvcmVfY3VtZWRpc3QgPC0KICBwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIG11dGF0ZShiaW5zPWN1dChzY29yZSwgYnJlYWtzID0gY3V0b2ZmcykpICU+JQogIG11dGF0ZShzY29yZT1hcy5udW1lcmljKHN0cl9yZW1vdmVfYWxsKGFzLmNoYXJhY3RlcihiaW5zKSwgIi4rLHxdIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgc3RhdF9lY2RmKHNpemU9MC44LCBhbHBoYT0wLjcpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIHlsYWIoIkZyYWN0aW9uIG9mIHVuYXNzaWduZWQgY2VsbHMiKSArCiAgeGxhYigiUHJlZGljdGlvbiBzY29yZSBjdXRvZmYiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB4bGltKDAsMSkgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGd1aWRlcyhjb2xvcj0ibm9uZSIpIAoKZ2dhcnJhbmdlKHByZWRpY3Rfc2NvcmVfaGlzdCwgcHJlZGljdF9zY29yZV9jdW1lZGlzdCwgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIHdpZHRocyA9IGMoMC44LCAxLjIpLAogICAgICAgICAgbGFiZWxzPWMoIkEiLCAiQiIpKSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJwcmVkaWN0aW9uX3Njb3JlX2Rpc3RyaWJ1dGlvbi5wZGYiKSwgaGVpZ2h0ID0gNiwgd2lkdGggPSAxMCkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTh9CmdncHVicjo6Z2dhcnJhbmdlKAogIHBsb3RsaXN0ID0gbGlzdCgKICAgIEZlYXR1cmVQbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZmVhdHVyZSA9ICJzY29yZV9DQ0EiICAsIGNvb3JkLmZpeGVkID0gVFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwKICAgIEZlYXR1cmVQbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZmVhdHVyZSA9ICJzY29yZV9MaWdlciIsIGNvb3JkLmZpeGVkID0gVFJVRSkgKyBnZ3RpdGxlKCJMaWdlciIpLAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0Nvbm9zIiwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkNvbm9zIikKICApLAogIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCBuY29sPTMsIG5yb3c9MQopICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInByZWRpY3Rpb25fc2NvcmVfdW1hcHMucGRmIiksIGhlaWdodCA9IDcsIHdpZHRoPTE0KQpgYGAKCgojIyBDZWxsIHR5cGUgY29tcG9zaXRpb24KCkNvbXBhcmUgY2VsbCB0eXBlIGZyYWN0aW9ucyAodyB1bmNlcnRhaW50eSkKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9CgpwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIGRyb3BfbmEoKSAlPiUKICBtdXRhdGUodG90LmNlbGxzPW4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkKSAlPiUKICBzdW1tYXJpc2UodG90LmxhYmVsID0gbigpLCB0b3QuY2VsbHMgPSBtYXgodG90LmNlbGxzKSwgbWVhbi5zY29yZT1tZWFuKHNjb3JlKSkgJT4lCiAgbXV0YXRlKGZyYWMubGFiZWw9dG90LmxhYmVsL3RvdC5jZWxscykgJT4lCiAgIyBiaW5kX3Jvd3Mob3JpZy5mcmFjLmRmKSAlPiUKICBnZ3Bsb3QoYWVzKG1ldGhvZCwgcHJlZGljdGVkLmlkKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yPW1lYW4uc2NvcmUsIHNpemU9ZnJhYy5sYWJlbCkpICsKICBzY2FsZV9jb2xvcl9jb250aW51b3VzKCkgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ImdyZXkiLCBoaWdoPSJibHVlIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpCiAgCgpgYGAKCkRvZXMgdGhlIHVuY2VydGFpbnR5IGRlcGVuZCBvbiB0aGUgc2l6ZSBvZiB0aGUgY2x1c3Rlcj8KYGBge3IsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD01fQoKcHJlZC5sYWJlbHMuZGYgJT4lCiAgZ3JvdXBfYnkobWV0aG9kKSAlPiUKICBkcm9wX25hKCkgJT4lCiAgbXV0YXRlKHRvdC5jZWxscz1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieShtZXRob2QsIHByZWRpY3RlZC5pZCkgJT4lCiAgc3VtbWFyaXNlKHRvdC5sYWJlbCA9IG4oKSwgdG90LmNlbGxzID0gbWF4KHRvdC5jZWxscyksIG1lYW4uc2NvcmU9bWVkaWFuKHNjb3JlKSwgc2Quc2NvcmU9bWFkKHNjb3JlKSkgJT4lCiAgbXV0YXRlKGZyYWMubGFiZWw9dG90LmxhYmVsL3RvdC5jZWxscykgJT4lCiAgIyBiaW5kX3Jvd3Mob3JpZy5mcmFjLmRmKSAlPiUKICBnZ3Bsb3QoYWVzKGZyYWMubGFiZWwsIG1lYW4uc2NvcmUsIGNvbG9yPW1ldGhvZCkpICsKICBnZW9tX3BvaW50KHNpemU9MikgKwogIGdlb21fZXJyb3JiYXIoYWVzKHltaW49bWVhbi5zY29yZS1zZC5zY29yZSwgeW1heD1tZWFuLnNjb3JlK3NkLnNjb3JlKSwgYWxwaGE9MC42KSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgZmFjZXRfZ3JpZCguIH4gbWV0aG9kKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpCiAgCmBgYAoKb3JpZ2luYWwgc2l6ZT8KYGBge3IsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD02fQoKcHJlZC5sYWJlbHMuZGYgJT4lCiAgZ3JvdXBfYnkobWV0aG9kKSAlPiUKICBkcm9wX25hKCkgJT4lCiAgbXV0YXRlKHRvdC5jZWxscz1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieShtZXRob2QsIHByZWRpY3RlZC5pZCkgJT4lCiAgc3VtbWFyaXNlKHRvdC5sYWJlbCA9IG4oKSwgdG90LmNlbGxzID0gbWF4KHRvdC5jZWxscyksIG1lYW4uc2NvcmU9bWVkaWFuKHNjb3JlKSwgc2Quc2NvcmU9bWFkKHNjb3JlKSkgJT4lCiAgbXV0YXRlKGZyYWMubGFiZWw9dG90LmxhYmVsL3RvdC5jZWxscykgJT4lCiAgbGVmdF9qb2luKHNlbGVjdChvcmlnLmZyYWMuZGYsIHByZWRpY3RlZC5pZCwgZnJhYy5sYWJlbCksIGJ5PSJwcmVkaWN0ZWQuaWQiLCBzdWZmaXg9YygiLmludCIsIi5vcmlnaW5hbCIpKSAlPiUKICAjIGJpbmRfcm93cyhvcmlnLmZyYWMuZGYpICU+JQogIGdncGxvdChhZXMoZnJhYy5sYWJlbC5vcmlnaW5hbCwgbWVhbi5zY29yZSwgY29sb3I9bWV0aG9kKSkgKwogIGdlb21fcG9pbnQoc2l6ZT0yKSArCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1tZWFuLnNjb3JlLXNkLnNjb3JlLCB5bWF4PW1lYW4uc2NvcmUrc2Quc2NvcmUpLCBhbHBoYT0wLjYpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsKICBmYWNldF9ncmlkKC5+bWV0aG9kKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpCmBgYAoKCgoKIyMgU2NvcmluZyBwZXIgY2VsbCB0eXBlCmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD02fQoKZG9kZ2UgPC0gcG9zaXRpb25fZG9kZ2Uod2lkdGg9MC45KQpwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QsIHByZWRpY3RlZC5pZCkgJT4lCiAgc3VtbWFyaXNlKHNjb3JlLm1lYW49bWVhbihzY29yZSwgbmEucm09RiksIHNjb3JlLmN2ID0gc2Qoc2NvcmUpL21lYW4oc2NvcmUpLCBuPW4oKSkgJT4lCiAgIyBnZ3Bsb3QoYWVzKHByZWRpY3RlZC5pZCwgc2NvcmUubWVhbiwgZmlsbD1tZXRob2QpKSArCiAgIyBnZW9tX2NvbCggcG9zaXRpb24gPSBkb2RnZSkgKwogIGdncGxvdChhZXMocHJlZGljdGVkLmlkLCBtZXRob2QpKSArCiAgZ2VvbV9wb2ludChhZXMoc2l6ZT1zY29yZS5tZWFuLCBjb2xvcj1zY29yZS5tZWFuKSkgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0id2hpdGUiLCBoaWdoID0gImJsdWUiKSArCiAgIyBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPXNjb3JlLm1lYW4tc2NvcmUuY3YsIHltYXg9c2NvcmUubWVhbitzY29yZS5jdiksIHBvc2l0aW9uPWRvZGdlLCB3aWR0aD0wLjI1KSAgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpCgpwcmVkLmxhYmVscy5kZiAlPiUKICBnZ3Bsb3QoYWVzKHByZWRpY3RlZC5pZCwgc2NvcmUsIGNvbG9yPW1ldGhvZCkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpCgpgYGAKCiMjIyBBZ3JlZW1lbnQgd2l0aCB1bnN1cGVydmlzZWQgY2x1c3RlcmluZyBvZiBBVEFDIGRhdGEKQ2FsY3VsYXRlIHdoaWNoIGZyYWN0aW9ucyBvZiBOTnMgaW4gYmluIGJhc2VkIGdyYXBoIG9mIEFUQUMgY2VsbHMgaGF2ZSB0aGUgc2FtZSBhbm5vdGF0aW9uCmBgYHtyfQprID0gNTAKYXRhYy5zZXUgPC0gRmluZE5laWdoYm9ycyhhdGFjLnNldSwgYXNzYXkgPSAiQVRBQyIsIHJlZHVjdGlvbiA9ICJTbmFwQVRBQyIsIGRpbXMgPSAxOjE1LCBrLnBhcmFtID0gaykKCmF0YWMubm4ubGlzdCA8LSBnZXROTmxpc3QoYXRhYy5zZXUpCgpzY29yZS5DQ0EgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYVsueCwxXSA9PSBwcmVkLmNjYVsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpCnNjb3JlLkNvbm9zIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jb25vc1sueCwxXSA9PSBwcmVkLmNvbm9zWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkKc2NvcmUuTGlnZXIgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmxpZ2VyWy54LDFdID09IHByZWQubGlnZXJbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKQoKa25uX3Njb3JlX2RmIDwtCiAgYXMuZGF0YS5mcmFtZShjYmluZChzY29yZS5Db25vcywgc2NvcmUuTGlnZXIsIHNjb3JlLkNDQSkpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPXN0cl9zdWJzZXQoY29sbmFtZXMoLiksICJzY29yZSIpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiS05OX3Njb3JlIikgJT4lCiAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSksCiAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkKCnF1YW50cyA9IHNlcSgwLDEsIGJ5ID0gMC4wNSkKQVVFQ0RGX2tubl9zY29yZSA8LSBrbm5fc2NvcmVfZGYgJT4lCiAgc3BsaXQoLiRtZXRob2QpICU+JQogIG1hcF9kYmwoIH4gLnggJT4lCiAgICAgIGFycmFuZ2UoS05OX3Njb3JlKSAlPiUgCiAgICAgIHtlY2RmKC4kS05OX3Njb3JlKShxdWFudHMpfSAlPiUgQVVDKHF1YW50cywuKQogICAgKQogIAprbm5fc2NvcmVfZGYgJT4lCiAgbXV0YXRlKEFVQz1BVUVDREZfa25uX3Njb3JlW21ldGhvZF0pICU+JQogIGdncGxvdChhZXMoS05OX3Njb3JlLCBjb2xvcj1tZXRob2QsIGZpbGw9bWV0aG9kKSkgKwogIHN0YXRfZWNkZihzaXplPTEpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGdlb21fdGV4dChkYXRhPS4gJT4lIGdyb3VwX2J5KG1ldGhvZCkgJT4lIHN1bW1hcmlzZShBVUM9bWF4KEFVQykpLCAKICAgICAgICAgICAgeD0wLjA1LCBoanVzdD0wLAogICAgICAgICAgICBhZXMobGFiZWw9Z2x1ZSgiQVVFQ0RGID0ge3JvdW5kKEFVQywgMyl9IiksIHk9YygwLjkwLCAwLjk1LCAxKSkpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9CgpmdWxsX2pvaW4ocHJlZC5sYWJlbHMuZGYsIGtubl9zY29yZV9kZikgJT4lCiAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPW1ldGhvZCkpICsKICBzdGF0X2VjZGYoKSArCiAgZmFjZXRfd3JhcCgicHJlZGljdGVkLmlkIikgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArCiAgY29vcmRfZml4ZWQoKQpgYGAKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTcsIG1lc3NhZ2U9RkFMU0V9CnBsb3RfS05OZWNkZiA8LSBmdW5jdGlvbihjbHVzdGVyKXsKICBmdWxsX2pvaW4ocHJlZC5sYWJlbHMuZGYsIGtubl9zY29yZV9kZikgJT4lCiAgICBmaWx0ZXIocHJlZGljdGVkLmlkPT1jbHVzdGVyKSAlPiUKICAgIGdncGxvdChhZXMoS05OX3Njb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgICBzdGF0X2VjZGYoc2l6ZT0wLjgpICsKICAgIGZhY2V0X3dyYXAoInByZWRpY3RlZC5pZCIpICsKICAgIHhsaW0oMCwxKSArIHlsaW0oMCwxKSArCiAgICBjb29yZF9maXhlZCgpICsKICAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCn0KCkRpbVBsb3RDbHVzdGVyIDwtIGZ1bmN0aW9uKGFubm90YXRpb25fY29sLCBjbHVzdGVyLCBsYWJlbCl7CiAgaGlnaGxpZ2h0ID0gd2hpY2goYXRhYy5zZXVAbWV0YS5kYXRhWyxhbm5vdGF0aW9uX2NvbF09PWNsdXN0ZXIpCiAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsY2VsbHMuaGlnaGxpZ2h0ID0gaGlnaGxpZ2h0LCBjb2xzLmhpZ2hsaWdodCA9ICJyZWQiLCBwdC5zaXplID0gMC4wMiwgc2l6ZXMuaGlnaGxpZ2h0ID0gMC4xKSArCiAgICBndWlkZXMoY29sb3I9Im5vbmUiKSArCiAgICBnZ3RpdGxlKGxhYmVsID0gbGFiZWwpCiAgfQoKVU1BUHNfY2x1c3RlciA8LSBmdW5jdGlvbihjbHVzdGVyKXsKICBnZ2FycmFuZ2UocGxvdGxpc3Q9aW1hcChsaXN0KENDQT0icHJlZGljdGVkLmlkX0NDQSIsIENvbm9zPSJwcmVkaWN0ZWQuaWRfQ29ub3MiLCBMaWdlcj0icHJlZGljdGVkLmlkX0xpZ2VyIiksIH4gRGltUGxvdENsdXN0ZXIoLngsIGNsdXN0ZXIsIGxhYmVsID0gLnkgKSksIG5jb2w9MywgbnJvdz0xKSAlPiUgYW5ub3RhdGVfZmlndXJlKGNsdXN0ZXIpCn0KCm1hcChjZWxsLnR5cGVzLCB+IGdnYXJyYW5nZShwbG90X0tOTmVjZGYoLngpLCBVTUFQc19jbHVzdGVyKC54KSwgbnJvdyA9IDIsIGhlaWdodHMgPSBjKDEsMC44KSkpCgpgYGAKCgojIyMjIFdoaWNoIGNlbGxzIGFyZSBpbmNvbnNpc3RlbnRseSBhbGlnbmVkPwpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTEwfQpwcmVkLmxhYmVscy5kZiAlPiUKICBzZWxlY3QobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGNlbGwpICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQ9aWZlbHNlKGlzLm5hKHByZWRpY3RlZC5pZCksICJub25lIiwgcHJlZGljdGVkLmlkKSkgJT4lCiAgZ2dwbG90KGFlcyh4PW1ldGhvZCwgc3RyYXR1bT1wcmVkaWN0ZWQuaWQsIGFsbHV2aXVtPWNlbGwsIGZpbGw9cHJlZGljdGVkLmlkLCBsYWJlbD1wcmVkaWN0ZWQuaWQpKSArCiAgZ2VvbV9mbG93KCkgKwogIGdlb21fc3RyYXR1bShjb2xvcj1OQSkgKwogIGdlb21fdGV4dChzdGF0PSJzdHJhdHVtIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KQpgYGAKCgojIyBDbG9zZXIgbG9vayBhdCBMaWdlcgpgYGB7cn0KbGlicmFyeShsaWdlcikKCnBsb3RCeURhdGFzZXRBbmRDbHVzdGVyKG1vZGVsLmxpZ2VyJG1vZGVsLCBjbHVzdGVycz1jKHNldF9uYW1lcyhhcy5jaGFyYWN0ZXIob3JpZy5STkEkYW5ub3RhdGlvbiksIGNvbG5hbWVzKG9yaWcuUk5BKSksIHNldF9uYW1lcyhhcy5jaGFyYWN0ZXIocHJlZC5saWdlclssJ3ByZWRpY3RlZC5pZF9MaWdlciddKSwgcm93bmFtZXMocHJlZC5saWdlcikpKSkKYGBgCgojIyBDb21wYXJlIGZlYXR1cmUgc2VsZWN0aW9uIHN0cmF0ZWd5CmBgYHtyfQptb2RlbC5jY2EudW5pb24gPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxDQ0FfdW5pb25faHZnX1BCTUNfU0NFbGlzdF8yMDE5MTEwNS5SRFMiKQpzZXUuY2NhLnVuaW9uIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJDQ0FfdW5pb25faHZnX1BCTUNfU0NFbGlzdF8yMDE5MTEwNS5SRFMiKQpzZXUubGlnZXIudW5pb24gPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckxpZ2VyX3VuaW9uX2h2Z19QQk1DX1NDRWxpc3RfMjAxOTExMDUuUkRTIikKc2V1LmNvbm9zLnVuaW9uIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJDb25vc191bmlvbl9odmdfUEJNQ19TQ0VsaXN0XzIwMTkxMTA1LlJEUyIpCgppbnRlZ3JhdGVfZmVhdHVyZXMgPC0gbW9kZWwuY2NhLnVuaW9uJG1vZGVsQGFuY2hvci5mZWF0dXJlcwoKaW50Lmxpc3QgPC0gbGlzdChDQ0E9c2V1LmNjYSwgTGlnZXI9c2V1LmxpZ2VyLCBDb25vcz1zZXUuY29ub3MpCgojIyBBZGQgdG8gQVRBQyBvYmplY3QgbWV0YS5kYXRhCnByZWQuY2NhLnVuaW9uIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY2NhLnVuaW9uLCAiQ0NBX3VuaW9uIiwgc2NvcmUuY29sID0gInByZWRpY3Rpb24uc2NvcmUubWF4IikKcHJlZC5saWdlci51bmlvbjwtIGdldFByZWRpY3RlZExhYmVscyhzZXUubGlnZXIudW5pb24sICJMaWdlcl91bmlvbiIpCnByZWQuY29ub3MudW5pb248LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmNvbm9zLnVuaW9uLCAiQ29ub3NfdW5pb24iKQoKIyBwcmVkLmNjYS51bmlvbiA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmNjYS51bmlvbiwgIkNDQS51bmlvbiIsIHNjb3JlLmNvbCA9ICJwcmVkaWN0aW9uLnNjb3JlLm1heCIpCiMgY2JpbmQocHJlZC5jY2EsIHByZWQuY2NhLnVuaW9uKQoKCmlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsKICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYS51bmlvbiwgcHJlZC5saWdlci51bmlvbiwgcHJlZC5jb25vcy51bmlvbikpCn0gZWxzZSB7CiAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpCn0KCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xNH0KZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcGxvdGxpc3QgPSBsaXN0KAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiICAsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwgICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ0NBX3VuaW9uIiAgLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ0NBIHVuaW9uIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0xpZ2VyIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9MaWdlcl91bmlvbiIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJMaWdlcl91bmlvbiIpLAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9Db25vcyIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDb25vcyIpLAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9Db25vc191bmlvbiIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDb25vcyB1bmlvbiIpCiAgICAgICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MiwgbnJvdz0zCikKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTh9CmdncHVicjo6Z2dhcnJhbmdlKAogIHBsb3RsaXN0ID0gbGlzdCgKICAgIEZlYXR1cmVQbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZmVhdHVyZSA9ICJzY29yZV9DQ0FfdW5pb24iICAsIGNvb3JkLmZpeGVkID0gVFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwKICAgIEZlYXR1cmVQbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZmVhdHVyZSA9ICJzY29yZV9MaWdlcl91bmlvbiIsIGNvb3JkLmZpeGVkID0gVFJVRSkgKyBnZ3RpdGxlKCJMaWdlciIpLAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0Nvbm9zX3VuaW9uIiwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkNvbm9zIikKICApLAogIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCBuY29sPTMsIG5yb3c9MQopIApgYGAKCmBgYHtyfQpvcmlnLmNvbXBvc2l0aW9uIDwtIG9yaWcuUk5BJGFubm90YXRpb24Kb3JpZy5mcmFjIDwtIHRhYmxlKG9yaWcuY29tcG9zaXRpb24pL2xlbmd0aChvcmlnLmNvbXBvc2l0aW9uKQoKb3JpZy5mcmFjLmRmIDwtIGRhdGEuZnJhbWUob3JpZy5mcmFjKSAlPiUKICBkcGx5cjo6cmVuYW1lKHByZWRpY3RlZC5pZD1vcmlnLmNvbXBvc2l0aW9uLCBmcmFjLmxhYmVsPUZyZXEpICU+JQogIG11dGF0ZShtZXRob2Q9Im9yaWdpbmFsLlJOQSIpCgpzY29yZV9jb2xzX3VuaW9uIDwtIHN0cl9zdWJzZXQoY29sbmFtZXMoYXRhYy5zZXVAbWV0YS5kYXRhKSwgJ3Njb3JlXy4rX3VuaW9uJykKbGFiZWxfY29sc191bmlvbiA8LSBzdHJfc3Vic2V0KGNvbG5hbWVzKGF0YWMuc2V1QG1ldGEuZGF0YSksICdwcmVkaWN0ZWQuaWRfLit1bmlvbicpCgpwcmVkLmxhYmVscy51bmlvbi5kZiA8LSBpbWFwKGxpc3QoQ0NBPXByZWQuY2NhLnVuaW9uLCBMaWdlcj1wcmVkLmxpZ2VyLnVuaW9uLCBDb25vcz1wcmVkLmNvbm9zLnVuaW9uKSwgfiAKICAgICAgcm93bmFtZXNfdG9fY29sdW1uKC54LCAiY2VsbCIpICU+JQogICAgICByZW5hbWVfYWxsKGZ1bnMoc3RyX3JlbW92ZSguLCBzdHJfYygiXyIsLnkpKSkpICU+JQogICAgICBtdXRhdGUobWV0aG9kPS55KQogICAgKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgbXV0YXRlKHNjb3JlPWlmZWxzZShpcy5uYShzY29yZV91bmlvbiksIDAsIHNjb3JlX3VuaW9uKSkKCnByZWRpY3Rfc2NvcmVfaGlzdCA8LSAKICBwcmVkLmxhYmVscy51bmlvbi5kZiAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlX3VuaW9uLCBmaWxsPW1ldGhvZCkpICsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjgsIGJpbnM9NDApICsKICBmYWNldF9ncmlkKG1ldGhvZCB+LikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgeGxhYigiTGFiZWwgcHJlZGljdGlvbiBzY29yZSIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKY3V0b2ZmcyA8LSBzZXEoMCwxLDAuMDUpCnByZWRpY3Rfc2NvcmVfY3VtZWRpc3QgPC0KICBwcmVkLmxhYmVscy51bmlvbi5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIG11dGF0ZShiaW5zPWN1dChzY29yZV91bmlvbiwgYnJlYWtzID0gY3V0b2ZmcykpICU+JQogIG11dGF0ZShzY29yZT1hcy5udW1lcmljKHN0cl9yZW1vdmVfYWxsKGFzLmNoYXJhY3RlcihiaW5zKSwgIi4rLHxdIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgc3RhdF9lY2RmKHNpemU9MC44LCBhbHBoYT0wLjcpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIHlsYWIoIkZyYWN0aW9uIG9mIHVuYXNzaWduZWQgY2VsbHMiKSArCiAgeGxhYigiUHJlZGljdGlvbiBzY29yZSBjdXRvZmYiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB4bGltKDAsMSkgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGd1aWRlcyhjb2xvcj0ibm9uZSIpIAoKZ2dhcnJhbmdlKHByZWRpY3Rfc2NvcmVfaGlzdCwgcHJlZGljdF9zY29yZV9jdW1lZGlzdCwgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIHdpZHRocyA9IGMoMC44LCAxLjIpLAogICAgICAgICAgbGFiZWxzPWMoIkEiLCAiQiIpKSAKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInByZWRpY3Rpb25fc2NvcmVfZGlzdHJpYnV0aW9uLnBkZiIpLCBoZWlnaHQgPSA2LCB3aWR0aCA9IDEwKQpgYGAKYGBge3J9CmsgPSA1MAphdGFjLnNldSA8LSBGaW5kTmVpZ2hib3JzKGF0YWMuc2V1LCBhc3NheSA9ICJBVEFDIiwgcmVkdWN0aW9uID0gIlNuYXBBVEFDIiwgZGltcyA9IDE6MTUsIGsucGFyYW0gPSBrKQoKYXRhYy5ubi5saXN0IDwtIGdldE5ObGlzdChhdGFjLnNldSkKCmNhbGN1bGF0ZV9LTk5fYWdyZWVtZW50IDwtIGZ1bmN0aW9uKHByZWQuY2NhLCBwcmVkLmNvbm9zLCBwcmVkLmxpZ2VyLCBrPTUwKXsKICBzY29yZS5DQ0EgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYVsueCwxXSA9PSBwcmVkLmNjYVsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpCiAgc2NvcmUuQ29ub3MgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNvbm9zWy54LDFdID09IHByZWQuY29ub3NbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKQogIHNjb3JlLkxpZ2VyIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5saWdlclsueCwxXSA9PSBwcmVkLmxpZ2VyWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkKICAKICBrbm5fc2NvcmVfZGYgPC0KICAgIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2NvcmUuQ29ub3MsIHNjb3JlLkxpZ2VyLCBzY29yZS5DQ0EpKSAlPiUKICAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JQogICAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RyX3N1YnNldChjb2xuYW1lcyguKSwgInNjb3JlIiksIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJLTk5fc2NvcmUiKSAlPiUKICAgIGRwbHlyOjptdXRhdGUoS05OX3Njb3JlPWlmZWxzZShpcy5uYShLTk5fc2NvcmUpLCAwLCBLTk5fc2NvcmUpLAogICAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkKICAKICBxdWFudHMgPSBzZXEoMCwxLCBieSA9IDAuMDUpCiAgQVVFQ0RGX2tubl9zY29yZSA8LSBrbm5fc2NvcmVfZGYgJT4lCiAgICBzcGxpdCguJG1ldGhvZCkgJT4lCiAgICBtYXBfZGJsKCB+IC54ICU+JQogICAgICAgIGFycmFuZ2UoS05OX3Njb3JlKSAlPiUgCiAgICAgICAge2VjZGYoLiRLTk5fc2NvcmUpKHF1YW50cyl9ICU+JSBBVUMocXVhbnRzLC4pCiAgICAgICkKICBsaXN0KGtubl9zY29yZV9kZiwgQVVFQ0RGX2tubl9zY29yZSkgIAp9CiAgCmtubl9hZ3JlZW1lbnQgPC0gY2FsY3VsYXRlX0tOTl9hZ3JlZW1lbnQocHJlZC5jY2EudW5pb24sIHByZWQuY29ub3MudW5pb24sIHByZWQubGlnZXIudW5pb24sIGs9NTApCmtubl9hZ3JlZW1lbnRbWzFdXSAlPiUKICBtdXRhdGUoQVVDPWtubl9hZ3JlZW1lbnRbWzJdXVttZXRob2RdKSAlPiUKICBnZ3Bsb3QoYWVzKEtOTl9zY29yZSwgY29sb3I9bWV0aG9kLCBmaWxsPW1ldGhvZCkpICsKICBzdGF0X2VjZGYoc2l6ZT0xKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBnZW9tX3RleHQoZGF0YT0uICU+JSBncm91cF9ieShtZXRob2QpICU+JSBzdW1tYXJpc2UoQVVDPW1heChBVUMpKSwgCiAgICAgICAgICAgIHg9MC4wNSwgaGp1c3Q9MCwKICAgICAgICAgICAgYWVzKGxhYmVsPWdsdWUoIkFVRUNERiA9IHtyb3VuZChBVUMsIDMpfSIpLCB5PWMoMC45MCwgMC45NSwgMSkpKQpgYGAKCiMjIyBUaG91Z2h0cwotIENvbm9zIHNjb3JlcyBhIGxvdCBvZiBjZWxscyB3aXRoIGhpZ2ggY29uZmlkZW5jZSwgYnV0IGZhaWxzIHRvIGFzc2lnbiBjZWxscyB0byBkaWZmaWN1bHQgY2x1c3RlcnMgCi0gQ0NBIHJlc2VtYmxlcyB0aGUgY29tcG9zaXRpb24gb2YgdGhlIFJOQSBkYXRhIGJldHRlciwgYnV0IGN1cmlvdXMgdGhhdCB0aGUgb3RoZXIgbWV0aG9kcyBpZGVudGlmeSB3YXkgbW9yZSAKCgoKCgoKCgoKCg==